{
  lib,
  config,
  pkgs,
  utils,
  ...
}:

let
  cfg = config.systemd;

  inherit (utils) systemdUtils;
  systemd-lib = utils.systemdUtils.lib;
in
{
  options.systemd = {

    # TODO: this is a bit dirty.
    # The value here gets added to the PATH of every service.
    # We could consider copying the systemd lib from NixOS and removing the bits
    # that are not relevant to us, like this option.
    package = lib.mkOption {
      type = lib.types.oneOf [
        lib.types.str
        lib.types.path
        lib.types.package
      ];
      default = pkgs.systemdMinimal;
    };

    globalEnvironment = lib.mkOption {
      type =
        with lib.types;
        attrsOf (
          nullOr (oneOf [
            str
            path
            package
          ])
        );
      default = { };
      example = {
        TZ = "CET";
      };
      description = lib.mdDoc ''
        Environment variables passed to *all* systemd units.
      '';
    };

    enableStrictShellChecks = lib.mkEnableOption "running shellcheck on the generated scripts for systemd units.";

    units = lib.mkOption {
      description = lib.mdDoc "Definition of systemd units.";
      default = { };
      type = systemdUtils.types.units;
    };

    packages = lib.mkOption {
      default = [ ];
      type = lib.types.listOf lib.types.package;
      example = lib.literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
      description = lib.mdDoc "Packages providing systemd units and hooks.";
    };

    targets = lib.mkOption {
      default = { };
      type = systemdUtils.types.targets;
      description = lib.mdDoc "Definition of systemd target units.";
    };

    services = lib.mkOption {
      default = { };
      type = systemdUtils.types.services;
      description = lib.mdDoc "Definition of systemd service units.";
    };

    sockets = lib.mkOption {
      default = { };
      type = systemdUtils.types.sockets;
      description = lib.mdDoc "Definition of systemd socket units.";
    };

    timers = lib.mkOption {
      default = { };
      type = systemdUtils.types.timers;
      description = lib.mdDoc "Definition of systemd timer units.";
    };

    paths = lib.mkOption {
      default = { };
      type = systemdUtils.types.paths;
      description = lib.mdDoc "Definition of systemd path units.";
    };

    mounts = lib.mkOption {
      default = [ ];
      type = systemdUtils.types.mounts;
      description = lib.mdDoc ''
        Definition of systemd mount units.
        This is a list instead of an attrSet, because systemd mandates the names to be derived from
        the 'where' attribute.
      '';
    };

    automounts = lib.mkOption {
      default = [ ];
      type = systemdUtils.types.automounts;
      description = lib.mdDoc ''
        Definition of systemd automount units.
        This is a list instead of an attrSet, because systemd mandates the names to be derived from
        the 'where' attribute.
      '';
    };

    slices = lib.mkOption {
      default = { };
      type = systemdUtils.types.slices;
      description = lib.mdDoc "Definition of slice configurations.";
    };

    generators = lib.mkOption {
      type = lib.types.attrsOf lib.types.path;
      default = { };
      example = {
        systemd-gpt-auto-generator = "/dev/null";
      };
      description = lib.mdDoc ''
        Definition of systemd generators.
        For each `NAME = VALUE` pair of the attrSet, a link is generated from
        `/etc/systemd/system-generators/NAME` to `VALUE`.
      '';
    };

    shutdown = lib.mkOption {
      type = lib.types.attrsOf lib.types.path;
      default = { };
      description = lib.mdDoc ''
        Definition of systemd shutdown executables.
        For each `NAME = VALUE` pair of the attrSet, a link is generated from
        `/etc/systemd/system-shutdown/NAME` to `VALUE`.
      '';
    };
  };

  config = {
    systemd = {
      targets.system-manager = {
        wantedBy = [ "default.target" ];
      };

      timers = lib.mapAttrs (name: service: {
        wantedBy = [ "timers.target" ];
        timerConfig.OnCalendar = service.startAt;
      }) (lib.filterAttrs (name: service: service.enable && service.startAt != [ ]) cfg.services);

      units =
        lib.mapAttrs' (n: v: lib.nameValuePair "${n}.path" (systemd-lib.pathToUnit v)) cfg.paths
        // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.service" (systemd-lib.serviceToUnit v)) cfg.services
        // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.slice" (systemd-lib.sliceToUnit v)) cfg.slices
        // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.socket" (systemd-lib.socketToUnit v)) cfg.sockets
        // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.target" (systemd-lib.targetToUnit v)) cfg.targets
        // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.timer" (systemd-lib.timerToUnit v)) cfg.timers
        // lib.listToAttrs (
          map (
            v:
            let
              n = utils.escapeSystemdPath v.where;
            in
            lib.nameValuePair "${n}.mount" (systemd-lib.mountToUnit v)
          ) cfg.mounts
        )
        // lib.listToAttrs (
          map (
            v:
            let
              n = utils.escapeSystemdPath v.where;
            in
            lib.nameValuePair "${n}.automount" (systemd-lib.automountToUnit v)
          ) cfg.automounts
        );
    };

    environment.etc =
      let
        allowCollisions = false;

        enabledUnits = lib.filterAttrs (_: unit: unit.enable) cfg.units;
      in
      {
        "systemd/system".source =
          pkgs.runCommand "system-manager-units"
            {
              preferLocalBuild = true;
              allowSubstitutes = false;
            }
            ''
              mkdir -p $out

              for i in ${toString (lib.mapAttrsToList (n: v: v.unit) enabledUnits)}; do
                fn=$(basename $i/*)
                if [ -e $out/$fn ]; then
                  if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
                    ln -sfn /dev/null $out/$fn
                  else
                    ${
                      if allowCollisions then
                        ''
                          mkdir -p $out/$fn.d
                          ln -s $i/$fn $out/$fn.d/overrides.conf
                        ''
                      else
                        ''
                          echo "Found multiple derivations configuring $fn!"
                          exit 1
                        ''
                    }
                  fi
                else
                  ln -fs $i/$fn $out/
                fi
              done

              ${lib.concatStrings (
                lib.mapAttrsToList (
                  name: unit:
                  lib.concatMapStrings (name2: ''
                    mkdir -p $out/'${name2}.wants'
                    ln -sfn '../${name}' $out/'${name2}.wants'/
                  '') (unit.wantedBy or [ ])
                ) enabledUnits
              )}

              ${lib.concatStrings (
                lib.mapAttrsToList (
                  name: unit:
                  lib.concatMapStrings (name2: ''
                    mkdir -p $out/'${name2}.requires'
                    ln -sfn '../${name}' $out/'${name2}.requires'/
                  '') (unit.requiredBy or [ ])
                ) enabledUnits
              )}
            '';
      };
  };
}
